﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace JingQiao.Ads.Infrastructure.Data.EF.Extension
{
    public static class QueryableExtensions
    {
        private static Expression<Func<TElement, bool>>
            GetWhereNotInExpression<TElement, TValue>(
            Expression<Func<TElement, TValue>> propertySelector,
            IEnumerable<TValue> values)
        {
            ParameterExpression p =
                propertySelector.Parameters.Single();

            if (!values.Any())
            {
                return e => true;
            }

            var unequals = values.Select(value =>
                (Expression)Expression.NotEqual(
                    propertySelector.Body,
                    Expression.Constant(value, typeof(TValue))
                )
            );

            var body = unequals.Aggregate<Expression>(
                (accumulate, unequal) => Expression.And(accumulate, unequal));

            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }

        private static Expression<Func<TElement, bool>>
            GetWhereInExpression<TElement, TValue>(
            Expression<Func<TElement, TValue>> propertySelector,
            IEnumerable<TValue> values)
        {
            ParameterExpression p =
                propertySelector.Parameters.Single();

            if (!values.Any())
            {
                return e => true;
            }

            var unequals = values.Select(value =>
                (Expression)Expression.NotEqual(
                    propertySelector.Body,
                    Expression.Constant(value, typeof(TValue))
                )
            );

            var body = unequals.Aggregate<Expression>(
                (accumulate, unequal) => Expression.And(accumulate, unequal));

            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }

        public static IQueryable<TElement>
            WhereNotIn<TElement, TValue>(this IQueryable<TElement> source,
            Expression<Func<TElement, TValue>> propertySelector,
            params TValue[] values)
        {
            return source.Where(GetWhereNotInExpression(
                propertySelector, values));
        }

        public static IQueryable<TElement>
            WhereNotIn<TElement, TValue>(this IQueryable<TElement> source,
            Expression<Func<TElement, TValue>> propertySelector,
            IEnumerable<TValue> values)
        {
            return source.Where(GetWhereNotInExpression(
                propertySelector, values));
        }

        public static IQueryable<TElement> WhereIn<TElement, TValue>(this IQueryable<TElement> source, Expression<Func<TElement, TValue>> propertySelector, params TValue[] values)
        {
            return source.Where(GetWhereInExpression(propertySelector, values));
        }

        public static IQueryable<TElement> WhereIn<TElement, TValue>(this IQueryable<TElement> source, Expression<Func<TElement, TValue>> propertySelector, IEnumerable<TValue> values)
        {
            //Agregar WHERE por los (iLote) primeros valores
            int iLote = 500;
            var queryOriginal = source;
            source = queryOriginal
                .Where(GetWhereInExpression(propertySelector, values.Take(iLote).ToList()));
            //Si hay más valores, se van agregando en lotes con UNION
            for (int i = iLote; i < values.Count(); i += iLote)
            {
                source = source.Union(
                    queryOriginal.WhereIn(propertySelector, values.Skip(i).Take(iLote).ToList())
                    );
            }
            return source;
            //return source.Where(GetWhereInExpression(propertySelector, values));
        }

    }

}
